﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text;
using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Driver;

namespace MongoCamp
{
    /// <summary>
    /// MongoCamp mxtension method class
    /// </summary>
    public static class ExtensionMethods
    {
        #region IEntityCollection Instance Save Method
        /// <summary>
        /// Save IEntityCollection
        /// </summary>
        /// <typeparam name="TEntityClass">the entity class type</typeparam>
        /// <param name="entity">the entity instance</param>
        /// <returns>WriteConcernResult</returns>
        public static WriteConcernResult Save<TEntityClass>(this TEntityClass entity) where TEntityClass : IEntityCollection
        {
            return MongoHelper.Save(entity.GetMongoServer(), entity.GetCollectionName(), entity);
        }
        #endregion IEntityCollection Instance Save Method

        #region object ToBsonValue Method

        internal static BsonValue ToBsonValue(this object value)
        {
            return (value is BsonValue ? (value as BsonValue) : BsonValue.Create(value));
        }

        #endregion object ToBsonValue Method

        #region MongoCursor<T> SetPageList Method

        internal static MongoCursor<T> SetPageList<T>(this MongoCursor<T> mongoCursor, IMongoFields fields, IMongoSortBy sortBy, int? pageSize, int? pageIndex)
        {
            if (fields != null)
                mongoCursor.SetFields(fields);

            if (sortBy != null)
                mongoCursor.SetSortOrder(sortBy);

            if (!pageSize.HasValue || pageSize.Value <= 0) return mongoCursor;

            mongoCursor.SetLimit(pageSize.Value);

            if (pageIndex.HasValue && pageIndex.Value > 1)
                mongoCursor.SetSkip((pageIndex.Value - 1) * pageSize.Value);

            return mongoCursor;
        }
        #endregion MongoCursor<T> ExtMethod

        #region IList<BsonDocument> ToJsonList Method

        /// <summary>
        /// the IList&lt;T&gt; is null or Count=0
        /// </summary>
        /// <typeparam name="T">TypeName</typeparam>
        /// <param name="list">IList&lt;T&gt;</param>
        /// <returns>bool</returns>
        public static bool IsNullOrEmpty<T>(this IList<T> list)
        {
            return (list == null || list.Count == 0);
        }

        /// <summary>
        /// BsonDocument is null or ElementCount=0
        /// </summary>
        /// <param name="document">BsonDocument</param>
        /// <returns>bool</returns>
        public static bool IsNullOrEmpty(this BsonDocument document)
        {
            return (document == null || document.ElementCount == 0);
        }

        /// <summary>
        /// IList&lt;BsonDocument&gt; List to Json String
        /// </summary>
        /// <param name="documents">IList&lt;BsonDocument&gt;</param>
        /// <param name="settings">JsonWriterSettings</param>
        /// <returns>json string</returns>
        public static string ToJsonList(this IList<BsonDocument> documents, JsonWriterSettings settings = null)
        {
            if (documents.IsNullOrEmpty())
                return "[]";

            var bsonArray = new BsonArray(documents);

            return (settings == null ? bsonArray.ToJson() : bsonArray.ToJson(settings));

        }

        #endregion IList<BsonDocument> ToJsonList Method

        #region IList<BsonDocument> ToDataTable Method

        /// <summary>
        /// BsonDocument to DataTable 
        /// </summary>
        /// <param name="bsonList"></param>
        /// <returns></returns>
        public static DataTable ToDataTable(this IList<BsonDocument> bsonList)
        {
            if (bsonList.IsNullOrEmpty())
                return GetTableSchema(null);

            var table = GetTableSchema(bsonList[0]);

            foreach (var entity in bsonList)
            {
                var newRow = table.NewRow();

                foreach (var item in entity.ToDictionary())
                    newRow[item.Key] = item.Value;

                table.Rows.Add(newRow);
            }

            return table;
        }

        /// <summary>
        /// Get Table Schema from BsonDocument
        /// </summary>
        /// <param name="document">BsonDocument</param>
        /// <param name="tableName">new table name</param>
        /// <returns>DataTable</returns>
        public static DataTable GetTableSchema(BsonDocument document, string tableName = "NewTable")
        {
            var table = new DataTable(tableName);

            if (document.IsNullOrEmpty()) return table;

            foreach (var item in document.ToDictionary())
            {
                table.Columns.Add(new DataColumn(item.Key, item.Value.GetType()));
            }

            return table;
        }

        #endregion IList<BsonDocument> ToDataTable Method

        #region IList<BsonDocument> ToDictionaryList Method

        /// <summary>
        /// To List&lt;Dictionary&lt;string, object&gt;&gt;
        /// </summary>
        /// <param name="documents">IList&lt;BsonDocument&gt;</param>
        /// <returns>IList&lt;Dictionary&lt;string, object&gt;&gt;</returns>
        public static IList<Dictionary<string, object>> ToDictionaryList(this IList<BsonDocument> documents)
        {
            return documents.IsNullOrEmpty() ? null : documents.Select(document => document.ToDictionary()).ToList();
        }

        #endregion

        #region TryGetValue & GetValueAs<T> from BsonDocument


        internal static object ChangeTypeTo<T>(this BsonValue bsonValue)
        {
            var conversionType = typeof(T);
            if (conversionType == typeof(string))
                return bsonValue.ToString();
            if (conversionType == typeof(DateTime) && bsonValue.IsBsonDateTime)
                return bsonValue.AsBsonDateTime.ToLocalTime();
            return Convert.ChangeType(bsonValue, conversionType);
        }

        /// <summary>
        /// Try Get Value from document
        /// </summary>
        /// <param name="document">document</param>
        /// <param name="name">name</param>
        /// <param name="value">return value</param>
        /// <typeparam name="T"></typeparam>
        /// <returns>bool</returns>
        public static bool TryGetValue<T>(this BsonDocument document, string name, out T value)
        {
            value = default(T);
            BsonValue rawValue;

            if (!document.TryGetValue(name, out rawValue)) return false;

            try
            {
                value = (T) rawValue.ChangeTypeTo<T>();
                return true;
            }
                // ReSharper disable once EmptyGeneralCatchClause
            catch
            { }

            return false;
        }

        /// <summary>
        /// get value as 
        /// </summary>
        /// <param name="document">document</param>
        /// <param name="name">name</param>
        /// <param name="defaultValue">default return value</param>
        /// <typeparam name="T"></typeparam>
        /// <returns>T</returns>
        public static T GetValueAs<T>(this BsonDocument document, string name, T defaultValue)
        {
            T value;
            return document.TryGetValue<T>(name, out value) ? value : defaultValue;
        }

        #endregion TryGetValue & GetValueAs<T>
    }
}
